12.7 Synchrone und asynchrone Operationen  
Bisher haben wir immer nur synchrone Operationen mit den Datenströmen ausgeführt. Bei einer synchronen Operation kann eine Anwendung erst dann weiterlaufen, wenn alle Lese- und Schreibzugriffe abgeschlossen sind. Häufig kommt es aber vor, dass ein Lese- oder Schreibvorgang längere Zeit in Anspruch nimmt. Dann sollte der Zugriff asynchron erfolgen, damit der Anwender mit der Anwendung weiterarbeiten kann, während die zeitaufwändige Operation in einem zweiten Thread parallel ausgeführt wird.
Nicht alle Klassen des Namespace System.IO ermöglichen asynchrone Operationen, sondern nur diejenigen, die von der Klasse Stream abgeleitet sind. Dazu zählt zum Beispiel auch die Klasse FileStream. Bereits bei der Instanziierung eines Stream-Objekts wird durch die Wahl eines passenden Konstruktors festgelegt, ob das Objekt asynchrone Operationen unterstützen soll.
| Public Sub New(String, FileMode, FileAccess, FileShare, _
|
| Integer, Boolean)
|
Ausschlaggebend ist der letzte Parameter vom Typ Boolean. Wird dem Parameter True übergeben, wird eine asynchrone Ein- bzw. Ausgabe ermöglicht, der bei den anderen Konstruktoren standardmäßig auf False gesetzt ist.
Eingeleitet werden die E/A-Operationen mit BeginWrite bzw. BeginRead, die Sie nun benutzen können.
| Public Overrides Function BeginRead(Byte(), Integer, Integer,
|
| AsyncCallback, Object) As IAsyncResult
|
| Public Overrides Function BeginWrite(Byte(), Integer, Integer,
|
| AsyncCallback, Object) As IAsyncResult
|
Bei deren Aufruf wird der Schreib- bzw. Lesevorgang in einem anderen Thread ausgeführt, während die Operationen im initiierenden Haupt-Thread weiter ausgeführt werden. Dem ersten Parameter werden die zu schreibenden Daten als Array übergeben, dem zweiten die Position, ab der die Daten geschrieben werden sollen, und dem dritten die Anzahl der zu schreibenden Bytes. Beide Methoden bieten darüber hinaus die Option, der Parameterliste einen Delegaten vom Typ AsyncCallback zu übergeben, der die zurückzurufende Methode im Haupt-Thread beschreibt. Der letzte Parameter schließlich dient zur Übergabe eines beliebigen Objekts. Wenn beispielsweise mehrere asynchrone Operationen parallel ausgeführt werden, können Sie über dieses Objekt die einzelnen Operationen voneinander unterscheiden.
Ist der weitere Programmablauf von der Beendigung der asynchronen Operation abhängig, können Sie den Haupt-Thread mit EndWrite oder EndRead blockieren, bis der asynchrone Vorgang abgeschlossen ist. Beide Methoden werden auf das FileStream-Objekt aufgerufen.
| Public Overrides Sub EndRead(IAsyncResult)
|
| Public Overrides Sub EndWrite(IAsyncResult)
|
Der Übergabeparameter ist dabei der, der von den Methoden BeginRead bzw. BeginWrite als Rückgabewert geliefert wird.
12.7.1 Beispielprogramm eines asynchronen Schreibvorgangs  
Das folgende Beispiel zeigt, wie eine asynchrone Schreiboperation implementiert wird.
| ' ----------------------------------------------------------
|
| ' Beispiel: ...\Kapitel 12\AsynchroneOperation
|
| ' ----------------------------------------------------------
|
| Imports System.IO
|
| Module Module1
|
| Dim fs As FileStream
|
| Sub Main()
|
| ' ca. 10 MByte großes Array füllen
|
| Console.Write("Array füllen ...")
|
| Dim byteArr(10000000) As Byte
|
| Dim rnd As New Random
|
| rnd.NextBytes(byteArr)
|
| Console.WriteLine("fertig!")
|
| ' Datei erzeugen
|
| Dim myfile As String = "C:\TestFile.txt"
|
| Console.WriteLine("Start ... ")
|
| ' FileStream initialisieren
|
| fs = New FileStream(myfile, FileMode.Create, _
|
| FileAccess.Write, FileShare.None, 256 * 1024, True)
|
| ' Festlegen der Rückrufmethode
|
| Dim delCallback As AsyncCallback = _
|
| New AsyncCallback(AddressOf CallbackMethod)
|
| ' Asynchrone Operation starten
|
| Dim result As IAsyncResult = fs.BeginWrite(byteArr, 0, _
|
| byteArr.Length, delCallback, Nothing)
|
| ' Irgendeine andere Operation
|
| Dim i As Integer
|
| For i = 0 To 99
|
| Console.WriteLine(i)
|
| Next
|
| ' Blockieren der Programmfortführung, bis der
|
| ' asynchrone Schreibvorgang beendet ist
|
| fs.EndWrite(result)
|
| ' jetzt kann der Stream geschlossen werden
|
| fs.Close()
|
| File.Delete(myfile)
|
| Console.ReadLine()
|
| End Sub
|
| ' diese Methode wird ausgeführt, sobald der asynchrone
|
| ' Schreibvorgang beendet ist
|
| Public Sub CallbackMethod(ByVal result As IAsyncResult)
|
| Console.WriteLine("Asynchroner Schreibvorgang beendet.")
|
| Console.WriteLine("Name der Datei: {0}", fs.Name)
|
| Console.WriteLine("Größe der Datei: {0}", fs.Length)
|
| End Sub
|
| End Module
|
Zunächst deklarieren wir ein byte-Array mit einer Größe von 10 000 001 Elementen. Das entspricht ungefähr 10 MByte. Der Zufallszahlengenerator Random füllt anschließend das Array mit x-beliebigen Zahlen.
Das Array soll nun in eine Datei geschrieben werden. Den Pfad zu dieser Datei übergeben wir der Variablen file. Anschließend wird das FileStream-Objekt initialisiert, die Rückrufmethode CallbackMethod bekannt gegeben und dann mit BeginWrite die asynchrone Schreib-operation gestartet. Die Schleife hinter dem asynchronen Aufruf dient nur dazu, sich selber von der parallelen Ausführung beider Operationen überzeugen zu können.
Vor Programmende soll der FileStream ordentlich geschlossen und die erzeugte Datei darüber hinaus gelöscht werden. Beide Vorgänge setzen jedoch voraus, dass der asynchrone Schreibvorgang abgeschlossen ist. Daher ist es unabdingbar, den Haupt-Thread vorher mit EndWrite zu blockieren, um das gewährleisten zu können.
Die Ausgabe könnte bei Ihnen wie in der folgenden Abbildung gezeigt aussehen.
 Hier klicken, um das Bild zu Vergrößern
Abbildung 12.6 Ausgabe der asynchronen Schreiboperation
|